﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Globalization;

namespace SqlMiM.Common.UserControls.PerfGraph
{
    public delegate string ValueToLabelHandler(double value);

    public class GraphTimeline : Control
    {

        private const double HorizontalLabelPadding = 2.0;
        private const double VerticalLabelPadding = 0.0;

        public static readonly DependencyProperty FillProperty = Shape.FillProperty.AddOwner(typeof(GraphTimeline));
        
        public static readonly DependencyProperty FirstPointVisibleProperty = 
            DependencyProperty.Register("FirstPointVisible", typeof(double), typeof(GraphTimeline), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));
        
        public static readonly DependencyProperty HashLabelConverterProperty = 
            DependencyProperty.Register("HashLabelConverter", typeof(ValueToLabelHandler), typeof(GraphTimeline), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
        
        public static readonly DependencyProperty MajorHashIntervalProperty = 
            DependencyProperty.Register("MajorHashInterval", typeof(int), typeof(GraphTimeline), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender));
        
        public static readonly DependencyProperty MinorHashIntervalProperty = 
            DependencyProperty.Register("MinorHashInterval", typeof(int), typeof(GraphTimeline), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender));
        
        public static readonly DependencyProperty NumPointsVisibleProperty = 
            DependencyProperty.Register("NumPointsVisible", typeof(double), typeof(GraphTimeline), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));
        
        public static readonly DependencyProperty OrientationProperty = 
            StackPanel.OrientationProperty.AddOwner(typeof(GraphTimeline), new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsRender));
        
        public static readonly DependencyProperty ShowHashLabelsProperty = 
            DependencyProperty.Register("ShowHashLabels", typeof(bool), typeof(GraphTimeline), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsRender));

        // Methods
        public GraphTimeline()
        {
            base.IsVisibleChanged += new DependencyPropertyChangedEventHandler(this.OnIsVisibleChanged);
            base.SizeChanged += new SizeChangedEventHandler(this.OnSizeChanged);
            this.UpdateHashPens();
        }

        /// <summary>
        /// Dessine un numéro à point donné
        /// </summary>
        private void DrawHashLabel(DrawingContext dc, Point point, double value, ref double lastLabelStart)
        {
            string str = (this.HashLabelConverter != null) ? this.HashLabelConverter(value) : value.ToString();
            
            if (!string.IsNullOrEmpty(str))
            {
                // Définition de la font (family style, weight
                Typeface typeface = new Typeface(base.FontFamily, base.FontStyle, base.FontWeight, base.FontStretch);

                // Text
                FormattedText formattedText = new FormattedText(str, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, base.FontSize, base.Foreground);
                
                Size size = new Size(formattedText.Width + 4.0, formattedText.Height + 0.0);
                
                // Doit ou non être dessiné
                bool mustbeDraw = false;

                if (this.Orientation == Orientation.Horizontal)
                {
                    if ((point.X + size.Width) < lastLabelStart)
                    {
                        mustbeDraw = true;
                        point.Y -= size.Height;
                        lastLabelStart = point.X;
                    }
                }
                else if ((point.Y + size.Height) < lastLabelStart)
                {
                    mustbeDraw = true;
                    point.X -= size.Width;
                    lastLabelStart = point.Y;
                }

                if (mustbeDraw)
                {
                    if (this.Fill != null)
                    {
                        dc.DrawRectangle(this.Fill, null, new Rect(point, size));
                    }
                    point.Offset(2.0, 0.0);
                    dc.DrawText(formattedText, point);
                }
            }
        }

        /// <summary>
        /// Dessine un petit trait 
        /// </summary>
        private void DrawHashMark(DrawingContext dc, Point startPoint, Point endPoint, Pen hashPen)
        {
            if (this.Orientation == Orientation.Horizontal)
            {
                if ((startPoint.X - (hashPen.Thickness / 2.0)) < 0.0)
                {
                    hashPen = hashPen.CloneCurrentValue();
                    hashPen.Thickness = (hashPen.Thickness + (startPoint.X * 2.0)) / 2.0;
                    startPoint.X = hashPen.Thickness / 2.0;
                    endPoint.X = startPoint.X;
                }
            }
            else if ((startPoint.Y - (hashPen.Thickness / 2.0)) < 0.0)
            {
                hashPen = hashPen.CloneCurrentValue();
                hashPen.Thickness = (hashPen.Thickness + (startPoint.Y * 2.0)) / 2.0;
                startPoint.Y = hashPen.Thickness / 2.0;
                endPoint.Y = startPoint.Y;
            }
            dc.DrawLine(hashPen, startPoint, endPoint);
        }

        private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (base.IsVisible)
            {
                base.InvalidateVisual();
            }
        }

        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            base.OnPropertyChanged(e);
            if (e.Property == Control.ForegroundProperty)
            {
                this.UpdateHashPens();
                if (base.IsVisible)
                {
                    base.InvalidateVisual();
                }
            }
        }


        protected override void OnRender(DrawingContext dc)
        {
            base.OnRender(dc);
            if ((base.IsVisible && (base.ActualWidth > 0.0)) && ((base.ActualHeight > 0.0) && (this.NumPointsVisible > 0.0)))
            {
                // Rend les points majeurs.
                // 0 = pas d'intervalle à éviter
                // 1.0 = longeur du trait
                this.RenderTimeline(dc, this.MajorHashInterval, 0, 1.0, this.MajorHashPen, this.ShowHashLabels);

                // Rend les points mineurs
                this.RenderTimeline(dc, this.MinorHashInterval, this.MajorHashInterval, 0.6, this.MinorHashPen, false);
            }
        }

        private void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (base.IsVisible)
            {
                base.InvalidateVisual();
            }
        }

        /// <summary>
        /// Rend un timeline complet
        /// </summary>
        /// <param name="dc"></param>
        /// <param name="interval">Intervalle </param>
        /// <param name="skipInterval">Intervalle à ne pas rendre (notamment pour les mineurs remplacés par les majeurs)</param>
        /// <param name="hashLength">taille de l'intervalle à dessiner</param>
        /// <param name="hashPen">Pen utilisé pour rendre l'intervalle</param>
        /// <param name="renderLabels">Rendre ou non le numéro</param>
        private void RenderTimeline(DrawingContext dc, int interval, int skipInterval, double hashLength, Pen hashPen, bool renderLabels)
        {
            if (interval > 0)
            {
                double actualWidth;
                double tmpInterval = interval;

                Point minPoint = new Point();
                Point maxPoint = new Point();
                
                Vector vector = new Vector();
                
                int firstNum = (((int)(this.FirstPointVisible / tmpInterval)) + 1) * interval;

                int lastNum = (int)(this.FirstPointVisible + this.NumPointsVisible);
                
                double num4 = (this.NumPointsVisible + this.FirstPointVisible) - firstNum;
                
                if (this.Orientation == Orientation.Horizontal)
                {
                    vector.X = (-tmpInterval * base.ActualWidth) / this.NumPointsVisible;
                    minPoint.X = (num4 * base.ActualWidth) / this.NumPointsVisible;
                    maxPoint.X = minPoint.X;
                    minPoint.Y = (1.0 - hashLength) * base.ActualHeight;
                    maxPoint.Y = base.ActualHeight;
                    actualWidth = base.ActualWidth;
                }
                else
                {
                    vector.Y = (-tmpInterval * base.ActualHeight) / this.NumPointsVisible;
                    minPoint.Y = (num4 * base.ActualHeight) / this.NumPointsVisible;
                    maxPoint.Y = minPoint.Y;
                    minPoint.X = (1.0 - hashLength) * base.ActualWidth;
                    maxPoint.X = base.ActualWidth;
                    actualWidth = base.ActualHeight;
                }

                for (int i = firstNum; i <= lastNum; i += interval)
                {
                    if ((skipInterval <= 0) || ((i % skipInterval) != 0))
                    {
                        // Desine le trait
                        this.DrawHashMark(dc, minPoint, maxPoint, hashPen);
                        
                        // Dessine le label
                        if (renderLabels)
                            this.DrawHashLabel(dc, minPoint, (double)i, ref actualWidth);
                    }
                    minPoint += vector;
                    maxPoint += vector;
                }
            }
        }

        private void UpdateHashPens()
        {
            this.MajorHashPen = new Pen(base.Foreground, 2.0);
            this.MinorHashPen = new Pen(base.Foreground, 1.0);
            if (base.Foreground.IsFrozen)
            {
                this.MajorHashPen.Freeze();
                this.MinorHashPen.Freeze();
            }
        }

        public Brush Fill
        {
            get
            {
                return (Brush)base.GetValue(FillProperty);
            }
            set
            {
                base.SetValue(FillProperty, value);
            }
        }

        public double FirstPointVisible
        {
            get
            {
                return (double)base.GetValue(FirstPointVisibleProperty);
            }
            set
            {
                base.SetValue(FirstPointVisibleProperty, value);
            }
        }

        public ValueToLabelHandler HashLabelConverter
        {
            get
            {
                return (ValueToLabelHandler)base.GetValue(HashLabelConverterProperty);
            }
            set
            {
                base.SetValue(HashLabelConverterProperty, value);
            }
        }

        /// <summary>
        /// Definit l'intervalle pour les marques majeurs
        /// </summary>
        public int MajorHashInterval
        {
            get
            {
                return (int)base.GetValue(MajorHashIntervalProperty);
            }
            set
            {
                base.SetValue(MajorHashIntervalProperty, value);
            }
        }

        private Pen MajorHashPen
        {
            get;
            set;
        }

        /// <summary>
        /// Définit l'intervalle pour les marques mineurs
        /// </summary>
        public int MinorHashInterval
        {
            get
            {
                return (int)base.GetValue(MinorHashIntervalProperty);
            }
            set
            {
                base.SetValue(MinorHashIntervalProperty, value);
            }
        }

        private Pen MinorHashPen
        {
            get;
            set;
        }

        private int NumPoints
        {
            get;
            set;
        }

        /// <summary>
        /// Nombre de points visibles
        /// </summary>
        public double NumPointsVisible
        {
            get
            {
                return (double)base.GetValue(NumPointsVisibleProperty);
            }
            set
            {
                base.SetValue(NumPointsVisibleProperty, value);
            }
        }

        /// <summary>
        /// Orientation du timeline
        /// </summary>
        public Orientation Orientation
        {
            get
            {
                return (Orientation)base.GetValue(OrientationProperty);
            }
            set
            {
                base.SetValue(OrientationProperty, value);
            }
        }

        /// <summary>
        /// Définit si on voit ou non les numérotations
        /// </summary>
        public bool ShowHashLabels
        {
            get
            {
                return (bool)base.GetValue(ShowHashLabelsProperty);
            }
            set
            {
                base.SetValue(ShowHashLabelsProperty, value);
            }
        }
    }




}
